package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

var DnsListServer = "https://dnsapi.cn/Record.List"
var DnsUpdateServer = "https://dnsapi.cn/Record.Modify"
var IpServer = "http://ns1.dnspod.net:6666"
var Tempfile = "/tmp/ddns_ip.mark"
var Token string
var Domain string
var Subdomain string

func init() {
	flag.StringVar(&Token, "t", "", "login token of you dnspod")
	flag.StringVar(&Domain, "d", "", "login token of you dnspod")
	flag.StringVar(&Subdomain, "s", "", "login token of you dnspod")
	flag.Parse()
}
func main() {
	if Domain == "" || Token == "" || Subdomain == "" {
		fmt.Println("token, domain and sub_domain should set")
		os.Exit(1)
	}
	var err error
	ip, err := getIV2p()
	checkErr(err)
	if ip == "" {
		fmt.Println("ip gotten is none")
		os.Exit(1)
	}

	needUpdate := false
	needWriteFile := false
	// get local ip
	localIp, err := ioutil.ReadFile(Tempfile)
	if err != nil {
		needUpdate = true
		needWriteFile = true
		fmt.Println("local ip mark file not exists")
	} else {
		if ip != strings.TrimSpace(string(localIp)) {
			fmt.Println("ip changed")
			needUpdate = true
			needWriteFile = true
		} else {
			fmt.Println("Ip is same")
		}
	}

	if needUpdate {
		fmt.Println("going to get dns record")
		record, err := getRecordId()
		if err != nil {
			checkErr(err)
		}

		fmt.Println("going to update dns record")
		err = updateDns(record, ip)
		checkErr(err)
	}

	// write ip to local
	if needWriteFile {
		fmt.Println("write ip to local file")
		err = ioutil.WriteFile(Tempfile, []byte(ip), 0640)
		checkErr(err)
	}
}

func checkErr(err error) {
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
}

func getIp() (string, error) {
	fmt.Println("getting self out ip ...")
	conn, err := net.DialTimeout("tcp", IpServer, time.Second*2)
	if err != nil {
		return "", err
	}
	defer conn.Close()

	out := make([]byte, 25)
	readNu, err := conn.Read(out)
	if err != nil {
		return "", err
	}
	return string(out[:readNu]), nil
}

func getIpV2() (string, error) {
    fmt.Println("gettting self out ip ...")
    resp, err := http.Get(IpServer)
    if err != nil{
        fmt.Println("err when get out ip from", IpServer, err)
	return "", err
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
       fmt.Println("err when get ip from retured body")
       return "", err
    }
    return string(body), nil
}


func updateDns(recordGetReturn RecordGetReturn, newIp string) error {
	recordUpdateData := url.Values{}
	recordUpdateData.Add("login_token", Token)
	recordUpdateData.Add("domain_id", recordGetReturn.Domain.Id)
	recordUpdateData.Add("sub_domain", Subdomain)
	recordUpdateData.Add("record_id", recordGetReturn.Records[0].Id)
	recordUpdateData.Add("record_type", recordGetReturn.Records[0].Type)
	recordUpdateData.Add("record_line_id", recordGetReturn.Records[0].LineId)
	recordUpdateData.Add("value", newIp)

	request, err := http.NewRequest("POST", DnsUpdateServer, strings.NewReader(recordUpdateData.Encode()))
	checkErr(err)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	client := &http.Client{}
	response, err := client.Do(request)
	if err != nil {
		return err
	}
	defer func() {
		err := response.Body.Close()
		if err != nil {
			fmt.Println(err.Error())
		}
	}()

	var result RecordUpdateReturn
	err = json.NewDecoder(response.Body).Decode(&result)
	checkErr(err)
	if result.Status.Code != "1" {
		return fmt.Errorf("update dns record fail: %s", result.Status.Message)
	}
	return nil
}

func getRecordId() (RecordGetReturn, error) {
	var recordGetReturn RecordGetReturn
	dataIn := url.Values{}
	dataIn.Add("login_token", Token)
	dataIn.Add("domain", Domain)
	dataIn.Add("sub_domain", Subdomain)
	dataIn.Add("record_type", "A")
	dataIn.Add("format", "json")
	dataIn.Add("length", "1")

	request, err := http.NewRequest("POST", DnsListServer, strings.NewReader(dataIn.Encode()))
	checkErr(err)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	// request.Header.Add("content-length", strconv.Itoa(len(dataIn.Encode())))

	client := &http.Client{}
	response, err := client.Do(request)
	if err != nil {
		return recordGetReturn, err
	}
	defer func() {
		err := response.Body.Close()
		if err != nil {
			fmt.Println(err.Error())
		}
	}()

	err = json.NewDecoder(response.Body).Decode(&recordGetReturn)
	if err != nil {
		return recordGetReturn, err
	}

	if recordGetReturn.Status.Code != "1" {
		return recordGetReturn, errors.New(recordGetReturn.Status.Message)
	}
	return recordGetReturn, nil
}

type RecordUpdateParam struct {
	LoginToken   string `json:"login_token"`
	Domain       string `json:"domain"`
	RecordId     string `json:"record_id"`
	RecordType   string `json:"record_type"`
	RecordLineId string `json:"record_line_id"`
	Value        string `json:"value"`
}

type RecordUpdateReturn struct {
	Status RecordStatus `json:"status"`
}

type RecordGetParam struct {
	LoginToken   string `json:"login_token"`
	Domain       string `json:"domain"`
	SubDomain    string `json:"sub_domain"`
	Length       uint   `json:"length"`
	RecordType   string `json:"record_type"`
	Format       string `json:"format"`
	ErrorOnEmpty string `json:"error_on_empty"`
}

type RecordGetReturn struct {
	Status  RecordStatus `json:"status"`
	Domain  RecordDomain `json:"domain"`
	Records []RecordItem `json:"records"`
}
type RecordStatus struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}
type RecordDomain struct {
	Id string `json:"id"`
}
type RecordItem struct {
	Id     string `json:"id"`
	Type   string `json:"type"`
	Value  string `json:"value"`
	LineId string `json:"line_id"`
}
